=begin
  #File:    flipper.rb
  #Author:  Andrew Stone
  #Created: 11.15.2007

	CSS Classes:
	span.current_page:  used to highlight current page within numeric page list
=end

class FlipperData
  attr_accessor(:previous_page, :next_page, :current_page, :total, :count, :search_by, :url)
   
  def initialize(params, total_rows, options)
    self.url = options[:list_url]
    self.count = total_rows

    current_page = 1
    current_page = params[:id].to_i if params[:id]
    self.current_page = current_page
    self.previous_page = nil
    self.total = 1
    if total_rows > options[:per_page_limit]
      self.total, remainder = total_rows.divmod(options[:per_page_limit])
      self.total += 1 if remainder > 0
      self.next_page = 2
      if current_page > 1
        self.previous_page = current_page - 1
        if current_page < self.total
          self.next_page = current_page + 1
        else
          self.next_page = nil
        end
      end
      get_search_by(params)
    end
  end

  private
    def get_search_by(params)
      @search_by = Hash.new
      sp = params.dup
      sp.delete(:action)
      sp.delete(:controller)
      sp.delete(:commit)
      sp.delete(:page)
      @search_by = @search_by.merge(sp) unless sp.empty?
    end
end

module FlipperView
  def self.included(base)
    base.send(:include, InstanceMethods)
  end

  module InstanceMethods
    def flipper(d)
      rvalue = %(<div id="pagination">)
      unless d.nil?
        rvalue << (d.total > 10 ? flip_controls(d) : page_controls(d))
      end
      rvalue << "</div>"
      rvalue
    end

    private
      def flip_controls(d)
        rvalue = ""
        if d.previous_page
          previous_url = d.url + "/" + d.previous_page.to_s
          rvalue << link_to('Previous page', previous_url)
          rvalue << "&nbsp;&nbsp;|&nbsp;&nbsp;"
        end

        page_no = 1
        page_no = (d.next_page - 1) if d.next_page
        page_no = (d.previous_page + 1) if d.previous_page

        rvalue << %(Page #{page_no} of #{d.total})

        if d.next_page
          page_no = d.next_page - 1
          rvalue << "&nbsp;&nbsp;|&nbsp;&nbsp;"
          next_url = d.url + "/" + d.next_page.to_s
          rvalue << link_to('Next page', next_url)
        end
        rvalue
      end

      def page_controls(d)
        rvalue = []
        return "Page 1 of 1" if d.total == 1
        1.upto(d.total) do |n|
          url = d.url + "/" + n.to_s
					if d.current_page == n	  
						n = '<span class="current_page" style="text-decoration: underline;">' + n.to_s + '</span>'
          end
          rvalue << link_to(n, url)
        end
        "Pages: " + rvalue.join("&nbsp;")
      end
  end
end

module Flipper
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def q(value)
        connection.quote_string(value)
    end

    # New Options:
    # options[:list_url]: Set the link for the Previous and Next buttons
    # options[:per_page_limit]: number represent the total rows shown on each page
    #                           default is 20
    # options[:searchby_fields]: array of field names to test the searchby param against; 
    #
    # Standard Rails options as used by find:
    # options[:conditions]
    # options[:order]
    # options[:join]
    #
    def pagination_find(params, options = {})
      raise "options[:list_url] is required" unless options[:list_url]

      limit = options[:per_page_limit] || 20
      offset = 0
      if params[:id]
        pg = params[:id].to_i
        offset = (pg - 1) * limit if pg > 1
      end

      xc = extended_conditions(params[:searchby_fields], options)

      count_sql = "select count(*) from ("
      sql = "select #{table_name}.id "
      sql_body = "from #{table_name} "

      add_joins!(sql_body, options)

      sql_body << "where #{sanitize_sql(options[:conditions])} " if options[:conditions]

      if xc.length > 0
        sql_body << (options[:conditions] ? "and " : "where ")
        sql_body << xc
      end
      
      limit_sql = <<-LIMIT
        limit #{limit}
        offset #{offset}
      LIMIT

      count_sql = count_sql + sql + sql_body
      add_order!(count_sql,options[:order])

      sql = sql + sql_body
      add_order!(sql, options[:order])
      sql << limit_sql

      total_rows = connection.execute(count_sql + ") as dcount")[0][0].to_i
      results = connection.execute(sql)
      
      rvalue = []
      results.each do |row|
        rvalue << find(row[0].to_i)
      end

      pager = FlipperData.new(params,total_rows,options)

      return pager, rvalue
    end

    private
      def extended_conditions(values,options={})
        return "" if values.nil? || values.strip.length == 0

        fields = match_fields(options)
        conditions = search_conditions(fields,values)
        return "(" + conditions + ")" if conditions.length > 0
        return ""
      end

      def match_fields(options)
        rvalue = content_columns.collect{|c| table_name + "." + c.name}
        rvalue = rvalue + options[:searchby_fields] if options[:searchby_fields]
        rvalue
      end

      def search_conditions(fields,values)
        rvalue = []
        val_array = split_search_values(values)
        val_array.each do |val|
          fvalue = []
          fields.each{|field|
            if val =~ /^\d+$/ || val.length < 3
              fvalue << "lower(#{field}) = lower('#{q(val)}')"
            else
              fvalue << "lower(#{field}) like lower('#{q(val)}%')"
            end
            }
          rvalue << fvalue.join(" or ")
        end
        rvalue.join(" and ")
      end

      def split_search_values(values)
        
        r = Regexp.new('(?:\s+)|((?:"(?:.+?)")|(?:\'(?:.+?)\'))').freeze
        a = values.split r
        a.delete_if { |v| v.nil? || v.length == 0 }
        a.each do |v|
          v.gsub!(/^['"]?(.+?)['"]?$/, '\1')
        end
        a
	
      end

      def cleanup_search_values(values)
        values = values.gsub('"','') if (values.count('"') % 2) != 0
        values = values.gsub("'",'') if (values.count("'") % 2) != 0
        values
      end
  end
end#Flipper end

ActiveRecord::Base.send :include, Flipper

ActionView::Base.send :include, FlipperView
